﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using GuiLabs.Undo;
using XmlStudio.DataLayer;
using XmlStudio.Enums;
using XmlStudio.Interfaces;
using XmlStudio.Models.Actions;
using XmlStudio.Models.Events;
using XmlStudio.Utilities;
using Tomers.WPF.Localization;

namespace XmlStudio.Models {

    /// <summary>
    /// Encapsulates all the core actions which are possible to do with the xml document.
    /// </summary>
    internal class XmlModel : IXmlModelWithNotifications {
        #region Fields
        #region Private
        private ActionManager actionManager = new ActionManager();
        private IBigXmlDocument xmlDocument = new BigXmlDocument();
        private string path = string.Empty;
        private BackgroundWorker validateXsdBw;
        private BackgroundWorker validateDtdBw;
        private Dictionary<IXPathNavigable, BigXmlNodeInternal> nodes = new Dictionary<IXPathNavigable, BigXmlNodeInternal>();
        private BigXmlNode root;
        private ApplicationMode mode = ApplicationMode.Xml;
        private bool modified;
        private XmlDocumentType documentType;
        #endregion
        #endregion

        #region Properties
        #region Public
        public XmlDocumentType DocumentType {
            get { return this.documentType; }
            set { this.documentType = value; }
        }

        /// <summary>
        /// Gets the collection of nodes loaded by this xmlModel.
        /// </summary>
        public IDictionary<IXPathNavigable, BigXmlNodeInternal> Nodes {
            get { return this.nodes; }
        }

        /// <summary>
        ///  Gets the file name of the current document.
        /// </summary>
        public string DocumentName {
            get { return string.IsNullOrEmpty(this.path) ? "New" : new FileInfo(this.path).Name; }
        }

        /// <summary>
        /// Gets the value indicating whether undo can be performed.
        /// </summary>
        public bool CanUndo { get { return this.actionManager.CanUndo; } }

        /// <summary>
        /// Gets the value indicating whether redo can be performed.
        /// </summary>
        public bool CanRedo { get { return this.actionManager.CanRedo; } }

        /// <summary>
        /// Gets the value indicating whether the xmlModel has been modified thus save is needed before exiting.
        /// </summary>
        public bool Modified {
            get { return this.modified; }
            set {
                this.modified = value;
                this.OnPropertyChanged("Modified");
            }
        }

        /// <summary>
        /// Gets the path to the loaded document.
        /// </summary>
        public string Path {
            get { return this.path; }

            private set {
                this.path = value;
                this.OnPropertyChanged("Path");
                //this.OnDocumentNameChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets the total node count.
        /// </summary>
        /// <value>The total node count.</value>
        public int TotalNodeCount {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the root node of the document.
        /// </summary>
        public BigXmlNode Root {
            get {
                this.root = this.xmlDocument.Root;
                return this.root;
            }

            set {
                this.xmlDocument.Root = value;
                this.root = value;
                this.OnPropertyChanged("Root");
            }
        }

        /// <summary>
        /// Gets the <see cref="XmlDocument"/> associated with the xmlModel which is used to create new nodes.
        /// </summary>
        public XmlDocument XmlDocument {
            get { return this.xmlDocument.Reader.XmlDocument; }
        }

        /// <summary>
        /// Gets the <see cref="IBigXmlReader"/> which is used to read nodes from the xml document.
        /// </summary>
        public IBigXmlReader Reader { get { return this.xmlDocument.Reader; } }

        /// <summary>
        /// Gets the number of loaded nodes.
        /// </summary>
        public int NodeCount { get { return this.nodes.Count; } }

        /// <summary>
        /// Gets or sets the application's mode.
        /// </summary>
        public ApplicationMode Mode {
            get { return this.mode; }
            set {
                this.mode = value;
                this.OnPropertyChanged("Mode");
            }
        }
        #endregion
        #endregion

        #region Methods
        #region Public
        #region Node collection handling
        /// <summary>
        /// Add <see cref="IXPathNavigable"/> as key and <see cref="BigXmlNodeInternal"/> as value to the collection of
        /// loaded nodes.
        /// </summary>
        /// <param name="xmlNode">Reference to <see cref="IXPathNavigable"/>.</param>
        /// <param name="node">Reference to the <see cref="BigXmlNodeInternal"/> associated with the <see cref="IXPathNavigable"/>.</param>
        public void AddNode(IXPathNavigable xmlNode, BigXmlNodeInternal node) {
            this.nodes.Add(xmlNode, node);
        }

        /// <summary>
        /// Adds node and all its descendant nodes to the collection of loaded nodes.
        /// </summary>
        /// <param name="node"></param>
        public void AddNodeWithChildren(BigXmlNodeInternal node) {
            foreach(BigXmlNodeInternal n in node.ChildNodes.Where(x => x != null)) {
                this.AddNodeWithChildren(n);
            }

            this.AddNode(node.XmlNode, node);
        }

        /// <summary>
        /// Removes node from the collection of loaded nodes.
        /// </summary>
        /// <param name="xmlNode"><see cref="IXPathNavigable"/> to remove.</param>
        public void RemoveNode(IXPathNavigable xmlNode) {
            this.nodes.Remove(xmlNode);
        }

        /// <summary>
        /// Removes node and all its descendant nodes from the collection of loaded nodes 
        /// (removes the subtree with root specified in parameter).
        /// </summary>
        /// <param name="xmlNode">Root node of the subtree to remove from the collection of loaded nodes.</param>
        public void RemoveNodeWithChildren(XmlNode xmlNode) {
            foreach(XmlNode node in xmlNode.ChildNodes) {
                this.RemoveNodeWithChildren(node);
            }

            this.nodes.Remove(xmlNode);
        }

        /// <summary>
        /// Clears the collection of loaded nodes.
        /// </summary>
        public void RemoveAllNodes() {
            this.nodes.Clear();
        }
        #endregion

        #region Validation
        /// <summary>
        /// Sets document type.
        /// </summary>
        /// <param name="rootElementName">Name of the root element.</param>
        /// <param name="publicId">Value for PUBLIC attribute (optional).</param>
        /// <param name="systemId">Path to dtd.</param>
        /// <param name="internalSubset">Internal subset (dtd) (optional).</param>
        public void SetDocumentType(string rootElementName, string publicId, string systemId, string internalSubset) {
            var action = new SetDocumentTypeAction(this, rootElementName, publicId, systemId, internalSubset);
            this.actionManager.RecordAction(action);
        }

        /// <summary>
        /// Removes DOCTYPE.
        /// </summary>
        public void RemoveDocumentType() {
            var action = new RemoveDocumentTypeAction(this);
            this.actionManager.RecordAction(action);
        }

        /// <summary>
        /// Validates document against DTD (if specified in the document). This validation is done asynchronously.
        /// </summary>
        public void ValidateDtdAsync() {
            this.OnStatusUpdated(new StatusUpdatedEventArgs(LanguageDictionary.GetDictionary(LanguageContext.Instance.Culture).Translate<string>("XmlModel.ValidatingDTD", "Text")));

            var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, ValidationType = ValidationType.DTD };
            settings.ValidationEventHandler += OnValidating;

            this.validateDtdBw = new BackgroundWorker { WorkerSupportsCancellation = true };
            this.validateDtdBw.DoWork += DoValidateDtdAsync;
            this.validateDtdBw.RunWorkerCompleted += OnValidateDtdAsyncCompleted;
            this.validateDtdBw.RunWorkerAsync(settings);
        }

        /// <summary>
        /// Validates document against DTD (if specified in the document).
        /// </summary>
        public void ValidateDtd() {
            this.OnStatusUpdated(new StatusUpdatedEventArgs(LanguageDictionary.GetDictionary(LanguageContext.Instance.Culture).Translate<string>("XmlModel.ValidatingDTD", "Text")));

            var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, ValidationType = ValidationType.DTD };
            settings.ValidationEventHandler += OnValidating;

            this.Validate(settings, null, null);
        }

        /// <summary>
        /// Cancels DTD validation.
        /// </summary>
        public void CancelValidateDtd() {
            if(this.validateDtdBw.IsBusy) {
                this.validateDtdBw.CancelAsync();
            }
        }

        /// <summary>
        /// Validates document against external <see cref="XmlSchemaSet"/>. Not implemented completely.
        /// </summary>
        /// <param name="schemaSet"><see cref="XmlSchemaSet"/> against which to validate the document.</param>
        public void ValidateExternalXsd(XmlSchemaSet schemaSet) {
            var settings = new XmlReaderSettings {
                ValidationType = ValidationType.Schema, Schemas = schemaSet, ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings
            };
        }

        /// <summary>
        /// Tries to validate the document against xsd if it is specified. It is done asynchronously.
        /// </summary>
        public void ValidateXsdAsync() {
            this.OnStatusUpdated(new StatusUpdatedEventArgs(LanguageDictionary.GetDictionary(LanguageContext.Instance.Culture).Translate<string>("XmlModel.ValidatingXSD", "Text")));

            var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema, DtdProcessing = DtdProcessing.Ignore, ValidationFlags = XmlSchemaValidationFlags.ProcessInlineSchema | XmlSchemaValidationFlags.ProcessSchemaLocation | XmlSchemaValidationFlags.ReportValidationWarnings };
            settings.ValidationEventHandler += OnValidating;

            this.validateXsdBw = new BackgroundWorker { WorkerSupportsCancellation = true };
            this.validateXsdBw.DoWork += DoValidateXsdAsync;
            this.validateXsdBw.RunWorkerCompleted += OnValidateXsdAsyncCompleted;
            this.validateXsdBw.RunWorkerAsync(settings);
        }

        /// <summary>
        /// Cancels XSD validation.
        /// </summary>
        public void CancelValidateXsd() {
            if(this.validateXsdBw.IsBusy) {
                this.validateXsdBw.CancelAsync();
            }
        }
        #endregion

        #region Open
        /// <summary>
        /// Opens the specified xml document.
        /// </summary>
        /// <param name="fileName">Path to open.</param>
        public void Open(string fileName) {
            this.actionManager.Clear();

            var fileInfo = new FileInfo(fileName);
            this.Path = fileInfo.FullName;

            this.xmlDocument = new BigXmlDocument(this.Path);

            Directory.SetCurrentDirectory(fileInfo.DirectoryName);
            this.documentType = this.xmlDocument.GetDocumentType();
        }
        #endregion

        #region Close
        /// <summary>
        /// Closes the xmlModel.
        /// </summary>
        public void Close() {
            this.OnClosing(EventArgs.Empty);
            this.OnClose();
            this.OnClosed(EventArgs.Empty);
        }
        #endregion

        #region Save
        /// <summary>
        /// Saves the document. If document has not been saved before, raises <see cref="DocumentNameRequired"/> event.
        /// </summary>
        public void Save(bool async) {
            if(string.IsNullOrEmpty(this.path)) {
                OnDocumentNameRequired(new DocumentNameRequiredEventArgs());
            }

            if(!string.IsNullOrEmpty(this.path)) {
                this.Save(this.path, async);
            }
        }

        /// <summary>
        /// Saves the edited document to the specified path.
        /// </summary>
        /// <param name="fileName">Path where to save the document.</param>
        /// <param name="async">if set to <c>true</c> [async].</param>
        public void Save(string fileName, bool async) {
            if(!string.IsNullOrEmpty(fileName)) {
                if(async) {
                    this.OnSaving(EventArgs.Empty);
                    this.Path = fileName;

                    this.saveBackgroundWorker = new BackgroundWorker { WorkerSupportsCancellation = true, WorkerReportsProgress = true };
                    this.saveBackgroundWorker.DoWork += new DoWorkEventHandler(saveBackgroundWorker_DoWork);
                    this.saveBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(saveBackgroundWorker_ProgressChanged);
                    this.saveBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(saveBackgroundWorker_RunWorkerCompleted);
                    this.saveBackgroundWorker.RunWorkerAsync(new SaveSettings { FileName = fileName, UpdateAddress = true });

                    // clear undo stack
                    this.actionManager.Clear();
                    this.OnSaved(EventArgs.Empty);
                    this.SetModified(false);
                } else {
                    this.OnSaving(EventArgs.Empty);
                    this.Path = fileName;
                    SaveCore(fileName, true);
                    // clear undo stack
                    this.actionManager.Clear();
                    this.OnSaved(EventArgs.Empty);
                    this.SetModified(false);
                    this.OnSaveCompleted(EventArgs.Empty);
                }
            }
        }

        void saveBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            //this.OnStatusUpdated(new StatusUpdatedEventArgs(e.ProgressPercentage.ToString()));
            this.OnProgressChanged(e);
        }

        private class SaveSettings {
            public string FileName { get; set; }
            public bool UpdateAddress { get; set; }
        }

        public void CancelSave() {
            if(this.saveBackgroundWorker.IsBusy) {
                this.saveBackgroundWorker.CancelAsync();
            }
        }

        void saveBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            this.OnSaveCompleted(EventArgs.Empty);
        }

        void saveBackgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
            if(e.Argument is SaveSettings) {
                this.OnStatusUpdated(new StatusUpdatedEventArgs(LanguageDictionary.GetDictionary(LanguageContext.Instance.Culture).Translate<string>("XmlModel.SavingFile", "Text")));
                var settings = e.Argument as SaveSettings;
                this.SaveCore(settings.FileName, settings.UpdateAddress);
                this.Reader.Path = settings.FileName;
                // clear undo stack
                this.actionManager.Clear();
                this.OnSaved(EventArgs.Empty);
                this.SetModified(false);
                this.OnStatusUpdated(new StatusUpdatedEventArgs(LanguageDictionary.GetDictionary(LanguageContext.Instance.Culture).Translate<string>("XmlModel.FileSaved", "Text")));
            }
        }

        private BackgroundWorker saveBackgroundWorker;

        /// <summary>
        /// Saves the document to the specified file.
        /// </summary>
        /// <param name="fileName">Name of the file where to save the document.</param>
        /// <param name="updateAddresses">Indicates whether to update addresses of those nodes which were moved hence their actual address is different from the original one.</param>
        private void SaveCore(string fileName, bool updateAddresses) {
            string tempPath;
            do {
                tempPath = fileName + Guid.NewGuid().ToString();
            } while(File.Exists(tempPath));

            FileStream fs = null;
            XmlWriter w = null;

            try {
                fs = new FileStream(tempPath, FileMode.Create);
                var settings = new XmlWriterSettings { Indent = true };
                w = XmlWriter.Create(fs, settings);

                //this.xmlDocument.Reader.WriteDoctype(w);
                this.xmlDocument.WriteXmlDocumentType(this.documentType, w);
                BigXmlNodeInternal.NodeCount = 0;
                BigXmlNodeInternal.Total = this.TotalNodeCount;
                ((BigXmlNodeInternal)this.Root).Save(w, this.xmlDocument.Reader, updateAddresses, this.saveBackgroundWorker);
            } catch(Exception) {
                File.Delete(tempPath);
                throw;
            } finally {
                if(w != null) {
                    w.Close();
                }

                if(fs != null) {
                    fs.Close();
                }
            }

            // overwrite the original file with the newly saved one
            var fi = new FileInfo(tempPath);
            fi.CopyTo(fileName, true);
            fi.Delete();
        }
        #endregion

        #region Get root element
        /// <summary>
        /// Gets the root element of the xml document.
        /// </summary>
        /// <returns>Reference to the root element.</returns>
        public virtual BigXmlNode GetRootElement() {
            if(this.root != null) {
                var oldRootInternal = this.root as BigXmlNodeInternal;
                if(oldRootInternal != null) {
                    this.RemoveNode(oldRootInternal.XmlNode);
                }
            }

            var rootInternal = this.Root as BigXmlNodeInternal;
            if(rootInternal != null) {
                this.nodes.Add(rootInternal.XmlNode, rootInternal);
                BigXmlNode rootElement = rootInternal;

                this.OnRootElementAdded(new RootElementAddedEventArgs<BigXmlNode>(rootElement));
                this.OnRawXmlChanged(new RawXmlChangedEventArgs());

                this.TotalNodeCount = this.xmlDocument.Reader.TotalNodeCount;
                return rootElement;
            }

            return null;
        }
        #endregion

        #region Expand node
        /// <summary>
        /// Expands the node, loads child elements from the specified range.
        /// </summary>
        /// <param name="node">Node to expand.</param>
        /// <param name="range">Range of child elements to load.</param>
        /// <param name="wholeSubtree">Indicates whether to also expand the whole subtree not just one level.</param>
        public void ExpandNode(BigXmlNode node, string range = "", bool wholeSubtree = false) {
            var nodeInternal = CheckNode(node);
            if(!wholeSubtree) {
                this.ExpandNodePrivate(nodeInternal, range);
            } else {
                this.ExpandWithSubtree(nodeInternal);
            }

            this.OnNodeExpanded(new NodeExpandedEventArgs<BigXmlNode>(node));
            this.OnRawXmlChanged(new RawXmlChangedEventArgs());
        }

        /// <summary>
        /// Expands all nodes in the document.
        /// </summary>
        public void ExpandAll() {
            var rootInternal = CheckNode(this.Root);

            this.ExpandWithSubtree(rootInternal);

            this.OnNodeExpanded(new NodeExpandedEventArgs<BigXmlNode>(rootInternal));
            this.OnRawXmlChanged(new RawXmlChangedEventArgs());
        }

        /// <summary>
        /// Expands node and all its descendant nodes.
        /// </summary>
        /// <param name="node">Root node of the subtree to expand.</param>
        private void ExpandWithSubtree(BigXmlNodeInternal node) {
            if(node.OriginalAddress != null) {
                this.RemoveNodes(node);

                var all = this.xmlDocument.Reader.GetAllFromAddress(node.Address);
                if(node.Parent != null) {
                    node.XmlNode.ParentNode.ReplaceChild(all.XmlNode, node.XmlNode);
                }

                node.XmlNode = all.XmlNode;
                node.ChildNodes.Clear();
                foreach(BigXmlNodeInternal child in all.ChildNodes) {
                    node.ChildNodes.Add(child);
                    child.SetParent(node);
                }

                this.FillNodes(node);
            } else {
                throw new InvalidOperationException("Cannot expand newly added node.");
            }
        }

        /// <summary>
        /// Adds the whole subtree identified with the root node.
        /// </summary>
        /// <param name="parent">Root (parent) node of the subtree.</param>
        private void FillNodes(BigXmlNodeInternal parent) {
            foreach(BigXmlNodeInternal child in parent.ChildNodes) {
                this.FillNodes(child);
                child.SetParent(parent);
            }

            this.AddNode(parent.XmlNode, parent);
        }
        #endregion

        #region Collapse node
        /// <summary>
        /// Collapses the specified node.
        /// </summary>
        /// <param name="node">Node to collapse.</param>
        public void CollapseNode(BigXmlNode node) {
            this.CollapseNodePrivate(node);

            this.OnNodeCollapsed(new NodeCollapsedEventArgs<BigXmlNode>(node));
            this.OnRawXmlChanged(new RawXmlChangedEventArgs());
        }
        #endregion

        #region Child count
        /// <summary>
        /// Gets the number of child nodes of the element at the specified address.
        /// </summary>
        /// <param name="address">Address of the element which child count to retrieve.</param>
        /// <returns>Number of child nodes.</returns>
        public int GetChildCount(int[] address) {
            return this.xmlDocument.Reader.GetChildCount(address);
        }
        #endregion

        #region Create element
        /// <summary>
        /// Creates new independent <see cref="BigXmlNode" /> which can be inserted inside the document.
        /// </summary>
        /// <param name="prefix">Prefix of the new node.</param>
        /// <param name="name">Name of the new node</param>
        /// <param name="namespaceURI">Namespace uri of the new node.</param>
        /// <returns>Reference to created <see cref="BigXmlNode"/>.</returns>
        public BigXmlNode CreateElement(string prefix, string name, string namespaceURI) {
            var doc = this.xmlDocument.Reader.XmlDocument;
            var element = doc.CreateElement(prefix, name, namespaceURI);
            var node = BigXmlNodeInternal.Create(element);
            node.SetModified(true);

            return node;
        }

        /// <summary>
        /// Creates new independent <see cref="BigXmlNode"/> which can be inserted inside the document.
        /// </summary>
        /// <param name="name">Name of the new node.</param>
        /// <returns>Reference to the created <see cref="BigXmlNode"/>.</returns>
        public BigXmlNode CreateElement(string name) {
            var doc = this.xmlDocument.Reader.XmlDocument;
            var element = doc.CreateElement(name);
            var node = BigXmlNodeInternal.Create(element);
            node.SetModified(true);

            return node;
        }

        /// <summary>
        /// Creates new independet <see cref="BigXmlNode"/> which encapsulates text node.
        /// </summary>
        /// <param name="text">Content of the new text node.</param>
        /// <returns>Reference to the created <see cref="BigXmlNode"/>.</returns>
        public BigXmlNode CreateTextNode(string text) {
            var doc = this.xmlDocument.Reader.XmlDocument;
            var textNode = doc.CreateTextNode(text);
            var node = BigXmlNodeInternal.Create(textNode);
            node.SetModified(true);

            return node;
        }
        #endregion

        #region Rename element
        /// <summary>
        /// Renames the specified element.
        /// </summary>
        /// <param name="node">Element to rename.</param>
        /// <param name="name">New name to set.</param>
        public void RenameElement(BigXmlNode node, string name) {
            var nodeInternal = CheckNode(node);
            var action = new RenameElementAction(this, nodeInternal, name);
            this.actionManager.RecordAction(action);
        }

        /// <summary>
        /// Renames the specified element.
        /// </summary>
        /// <param name="node">Element to rename.</param>
        /// <param name="localName">New localName to set.</param>
        /// <param name="namespaceUri">Namespace uri of the element.</param>
        /// <param name="prefix">Prefix for the namespace uri.</param>
        public void RenameElement(BigXmlNode node, string localName, string namespaceUri, string prefix) {
            var nodeInternal = CheckNode(node);
            var action = new RenameElementAction(this, nodeInternal, localName, namespaceUri, prefix);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Add attribute
        /// <summary>
        /// Adds an attribute to the <see cref="BigXmlNode"/>.
        /// </summary>
        /// <param name="node"><see cref="BigXmlNode"/> on which to add the attribute.</param>
        /// <param name="name">Name of the attribute to add.</param>
        /// <param name="value">Value of the attribute.</param>
        public void AddAttribute(BigXmlNode node, string name, string value) {
            var nodeInternal = CheckNode(node);
            var action = new AddAttributeAction(this, nodeInternal, name, value);
            this.actionManager.RecordAction(action);
        }

        /// <summary>
        /// Adds an attribute to the <see cref="BigXmlNode"/>.
        /// </summary>
        /// <param name="node"><see cref="BigXmlNode"/> on which to add the attribute.</param>
        /// <param name="prefix">Prefix of the attribute.</param>
        /// <param name="localName">Local name of the attribute to add.</param>
        /// <param name="namespaceUri">Namespace uri of the attribute to add.</param>
        /// <param name="value">Value of the attribute.</param>
        public void AddAttribute(BigXmlNode node, string prefix, string localName, string namespaceUri, string value) {
            var nodeInternal = CheckNode(node);
            var action = new AddAttributeAction(this, nodeInternal, prefix, localName, namespaceUri, value);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Remove attribute
        /// <summary>
        /// Removes an attribute from <see cref="BigXmlNode"/>.
        /// </summary>
        /// <param name="node"><see cref="BigXmlNode"/> from which to remove the attribute.</param>
        /// <param name="name">Name of the attrbiute which to remove.</param>
        public void RemoveAttribute(BigXmlNode node, string name) {
            var nodeInternal = CheckNode(node);
            var action = new RemoveAttributeAction(this, nodeInternal, name);
            this.actionManager.RecordAction(action);
        }

        /// <summary>
        /// Removes an attribute from <see cref="BigXmlNode"/>.
        /// </summary>
        /// <param name="node"><see cref="BigXmlNode"/> from which to remove the attribute.</param>
        /// <param name="prefix">Prefix of the namespace uri.</param>
        /// <param name="localName">Local name of the attribute to remove.</param>
        /// <param name="namespaceUri">Namespace of the attribute to remove.</param>
        public void RemoveAttribute(BigXmlNode node, string prefix, string localName, string namespaceUri) {
            var nodeInternal = CheckNode(node);
            var action = new RemoveAttributeAction(this, nodeInternal, prefix, localName, namespaceUri);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Get raw xml
        /// <summary>
        /// Gets raw xml text of currently expanded <see cref="BigXmlNode"/>s.
        /// </summary>
        /// <returns>String with xml of currently expanded <see cref="BigXmlNode"/>s.</returns>
        public string GetRawXml() {
            if(this.xmlDocument.Root != null) {
                var rootInternal = CheckNode(this.xmlDocument.Root);

                if(rootInternal != null) {
                    var sb = new StringBuilder();
                    using(var tw = new XmlTextWriter(new StringWriter(sb)) { Formatting = Formatting.Indented }) {
                        this.xmlDocument.WriteXmlDocumentType(this.documentType, tw);
                        rootInternal.XmlNode.WriteTo(tw);
                    }

                    return sb.ToString();
                }
            }

            return string.Empty;
        }
        #endregion

        #region Add child node
        /// <summary>
        /// Adds the childNode as a child node of the specified parent node.
        /// </summary>
        /// <param name="parentNode">Parent node where to add child node.</param>
        /// <param name="childNode">Child node to add.</param>
        /// <param name="mode">Insert mode.</param>
        /// <param name="refChild">Reference child node when insert mode is Before or After.</param>
        public void AddChildNode(BigXmlNode parentNode, BigXmlNode childNode, InsertMode mode, BigXmlNode refChild) {
            var parentNodeInternal = CheckNode(parentNode);
            var childNodeInternal = CheckNode(childNode);
            var refChildInternal = CheckNode(refChild);

            var action = new AddChildNodeAction(this, parentNodeInternal, childNodeInternal, mode, refChildInternal);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Create root element
        /// <summary>
        /// Creates a new root element.
        /// </summary>
        /// <remarks>The old root element must be removed before creating a new one otherwise ApplicationException is thrown.</remarks>
        /// <param name="name">Name of the new root element.</param>
        public BigXmlNode CreateRootElement(string name) {
            var action = new CreateRootElementAction(this, name);
            this.actionManager.RecordAction(action);
            return this.Root;
        }

        /// <summary>
        /// Creates a new root element.
        /// </summary>
        /// <param name="prefix">New element's prefix.</param>
        /// <param name="localName">Element's name.</param>
        /// <param name="namespaceUri">Element's namespace uri.</param>
        public BigXmlNode CreateRootElement(string prefix, string localName, string namespaceUri) {
            var action = new CreateRootElementAction(this, prefix, localName, namespaceUri);
            this.actionManager.RecordAction(action);
            return this.Root;
        }
        #endregion

        #region Remove node
        public void RemoveRootNode() {
            this.xmlDocument.RemoveRootNode();
        }

        /// <summary>
        /// Removes specified node from its parent.
        /// </summary>
        /// <param name="nodeToRemove">Node to remove.</param>
        /// <param name="removeSubtree">Indicates whether to remove whole subtree with the node or whether to append all child nodes of removed node to the parent node of removed node.</param>
        public void RemoveNode(BigXmlNode nodeToRemove, bool removeSubtree) {
            if(!removeSubtree && nodeToRemove.NodeState != ExpandState.Expanded) {
                throw new InvalidOperationException("Cannot remove node without its subtree if it is not fully expanded.");
            }
            var nodeToRemoveInternal = CheckNode(nodeToRemove);
            if(removeSubtree) {
                var action = new RemoveNodeAction(this, nodeToRemoveInternal);
                this.actionManager.RecordAction(action);
            } else {
                // don't remove the subtree

                var parent = nodeToRemove.Parent;

                // if removing the root node with more than one child and not also the subtree beneath, throw an exception (this is not allowed!)
                if(parent == null && nodeToRemove.TotalChildCount > 1) {
                    throw new InvalidOperationException("Cannot remove root node with more than one child!");
                }

                // if removing the root node with no or one child node, remove the root directly without
                if(parent == null && nodeToRemove.TotalChildCount <= 1) {
                    var childNode = nodeToRemove.TotalChildCount > 0 ? nodeToRemove.ChildNodes[0] : null;

                    using(var t = this.actionManager.CreateTransaction()) {
                        t.IsDelayed = false;

                        var remove = new RemoveRootWithOneChildAction(this, CheckNode(nodeToRemove));
                        this.actionManager.RecordAction(remove);
                    }

                    return;
                }

                var referenceNode = nodeToRemove.Parent.GetPreviousSibling(nodeToRemove);
                var insertMode = InsertMode.After;

                if(referenceNode == null) {
                    insertMode = InsertMode.First;
                }

                var parentInternal = CheckNode(parent);
                var referenceNodeInternal = CheckNode(referenceNode);

                var nodesToInsert = nodeToRemove.ChildNodes.ToList();
                using(var t = this.actionManager.CreateTransaction()) {
                    t.IsDelayed = false;


                    foreach(var node in nodesToInsert) {
                        var nodeInternal = CheckNode(node);
                        var remove = new RemoveNodeAction(this, nodeInternal);
                        this.actionManager.RecordAction(remove);

                        //AddChildNodeAction add = new AddChildNodeAction(this, parentInternal, nodeInternal, insertMode, referenceNodeInternal);
                        //this.actionManager.RecordAction(add);

                        //referenceNodeInternal = nodeInternal;
                        //insertMode = InsertMode.After;
                    }

                    var action = new RemoveNodeAction(this, nodeToRemoveInternal);
                    this.actionManager.RecordAction(action);

                    foreach(var node in nodesToInsert) {
                        var nodeInternal = CheckNode(node);

                        var add = new AddChildNodeAction(this, parentInternal, nodeInternal, insertMode, referenceNodeInternal);
                        this.actionManager.RecordAction(add);

                        referenceNodeInternal = nodeInternal;
                        insertMode = InsertMode.After;
                    }
                }
            }
        }
        #endregion

        #region Change node order
        /// <summary>
        /// Changes the order of the specified node.
        /// </summary>
        /// <param name="nodeToMove"><see cref="BigXmlNode"/> to move in the collection of child nodes of a certain parent node.</param>
        /// <param name="previousSibling">Reference node.</param>
        public void ChangeNodeOrder(BigXmlNode nodeToMove, BigXmlNode previousSibling) {
            var nodeToMoveInternal = CheckNode(nodeToMove);
            var previousSiblingInternal = CheckNode(previousSibling);
            var action = new ChangeNodeOrderAction(this, previousSiblingInternal, nodeToMoveInternal);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Modify text node
        /// <summary>
        /// Modifies the value of the specified text node which is wrapped in <see cref="BigXmlNode"/>.
        /// </summary>
        /// <param name="node"><see cref="BigXmlNode"/> with the text node to modify.</param>
        /// <param name="text">New value to set.</param>
        public void ModifyTextNode(BigXmlNode node, string text) {
            var nodeInternal = CheckNode(node);
            var action = new ModifyTextNodeAction(this, nodeInternal, text);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Select nodes
        /// <summary>
        /// Selects a set of xml nodes according to the xpath query.
        /// </summary>
        /// <param name="xpath">XPath query.</param>
        public IEnumerable<BigXmlNode> SelectNodes(string xpath) {
            if(this.Root != null) {
                var rootInternal = this.Root as BigXmlNodeInternal;
                if(rootInternal != null) {
                    var list = SelectNodesCore(xpath, rootInternal).ToList<BigXmlNode>();
                    this.OnNodesSelected(new NodesSelectedEventArgs<BigXmlNode>(list));
                    return list;
                }
            }

            return new List<BigXmlNode>();
        }

        /// <summary>
        /// Selects a set of xml nodes according to the xpath query starting at the specified node.
        /// </summary>
        /// <param name="xpath">XPath query.</param>
        /// <param name="node">Node where to start evaluating the query.</param>
        /// <returns>Collection of nodes selected by the query.</returns>
        public IList<BigXmlNodeInternal> SelectNodesCore(string xpath, BigXmlNodeInternal node) {
            var list = new List<BigXmlNodeInternal>();
            var selectedNodes = node.XmlNode.SelectNodes(xpath);
            if(selectedNodes != null) {
                foreach(XmlNode n in selectedNodes) {
                    BigXmlNodeInternal nodeInternal;
                    if(this.nodes.TryGetValue(n, out nodeInternal)) {
                        list.Add(nodeInternal);
                    }
                }
            }

            return list;
        }
        #endregion

        #region Move & Copy Subtree
        /// <summary>
        /// Moves existing subtree to the different position in the xml tree.
        /// </summary>
        /// <param name="subtreeToMove">Root node of the subtree to move.</param>
        /// <param name="newParent">New parent node of the moving subtree.</param>
        /// <param name="insertMode">Insert mode.</param>
        /// <param name="referenceNode">Reference node.</param>
        public void MoveSubtree(BigXmlNode subtreeToMove, BigXmlNode newParent, InsertMode insertMode, BigXmlNode referenceNode) {
            var subtreeToMoveInternal = CheckNode(subtreeToMove);
            var newParentInternal = CheckNode(newParent);
            var referenceNodeInternal = CheckNode(referenceNode);

            var action = new MoveSubtreeAction(this, subtreeToMoveInternal, newParentInternal, insertMode, referenceNodeInternal);
            this.actionManager.RecordAction(action);
        }

        /// <summary>
        /// Copies existing subtree to the specified destination in the xml tree.
        /// </summary>
        /// <param name="subtreeToCopy">Root of the subtree to copy.</param>
        /// <param name="destination">Parent node where to copy the subtree.</param>
        /// <param name="insertMode">Insert mode.</param>
        /// <param name="referenceNode">Reference node.</param>
        public void CopySubtree(BigXmlNode subtreeToCopy, BigXmlNode destination, InsertMode insertMode, BigXmlNode referenceNode) {
            var subtreeToCopyInternal = CheckNode(subtreeToCopy);
            var destinationInternal = CheckNode(destination);
            var referenceNodeInternal = CheckNode(referenceNode);

            var action = new CopySubtreeAction(this, subtreeToCopyInternal, destinationInternal, insertMode, referenceNodeInternal);
            this.actionManager.RecordAction(action);
        }
        #endregion

        #region Update simpleTypes
        /// <summary>
        /// Updates element's simpleTypes to element has only simpleTypes contained in simpleTypes collection.
        /// </summary>
        /// <param name="element">Element where to update simpleTypes.</param>
        /// <param name="attributes">The attributes.</param>
        public void UpdateAttributes(BigXmlNode element, IDictionary<string, IBigXmlAttribute> attributes) {
            if(attributes == null) {
                throw new ArgumentNullException("simpleTypes");
            }

            var node = CheckNode(element);

            var oldAttributes = node.Attributes;

            IEqualityComparer<IBigXmlAttribute> comparer = new LambdaComparer<IBigXmlAttribute>(
                                         (a, b) => (a.LocalName == b.LocalName
                                         && a.Prefix == b.Prefix
                                         && a.NamespaceURI == b.NamespaceURI
                                         && a.Value == b.Value));

            var attributesToRemove = from x in oldAttributes.Values
                                     where !attributes.Values.Contains(x, comparer)
                                     select x;

            var attributesToAdd = from x in attributes.Values
                                  where !oldAttributes.Values.Contains(x, comparer)
                                  select x;

            using(var t = this.actionManager.CreateTransaction()) {
                t.IsDelayed = false;
                foreach(var att in attributesToRemove) {
                    try {
                        var action = new RemoveAttributeAction(this, node, att.Prefix, att.LocalName, att.NamespaceURI);
                        this.actionManager.RecordAction(action);
                    } catch(Exception) {
                        t.Rollback();
                        throw;
                    }
                }

                foreach(var att in attributesToAdd) {
                    try {
                        var action = new AddAttributeAction(this, node, att.Prefix, att.LocalName, att.NamespaceURI, att.Value);
                        this.actionManager.RecordAction(action);
                    } catch(Exception) {
                        t.Rollback();
                        throw;
                    }
                }
            }

            this.OnRawXmlChanged(new RawXmlChangedEventArgs());
        }
        #endregion

        #region Undo & Redo
        /// <summary>
        /// Undoes the last action.
        /// </summary>
        public void Undo() {
            if(this.actionManager.CanUndo) {
                this.actionManager.Undo();
            }
        }

        /// <summary>
        /// Redoes the undone action.
        /// </summary>
        public void Redo() {
            if(this.actionManager.CanRedo) {
                this.actionManager.Redo();
                this.OnRawXmlChanged(new RawXmlChangedEventArgs());
            }
        }
        #endregion

        #region BeginTransaction
        /// <summary>
        /// Creates transaction reference.
        /// </summary>
        /// <returns>Reference to <see cref="ITransaction"/>.</returns>
        public ITransaction BeginTransaction() {
            return this.actionManager.CreateTransaction(false);
        }
        #endregion

        #region Event handlers
        /// <summary>
        /// Executed after node is expanded.
        /// </summary>
        /// <param name="e">The event args.</param>
        public virtual void OnNodeExpanded(NodeExpandedEventArgs<BigXmlNode> e) {
            var nodeExpanded = this.NodeExpanded;
            if(nodeExpanded != null) {
                nodeExpanded(this, e);
            }
        }

        /// <summary>
        /// Executed after node is collapsed.
        /// </summary>
        /// <param name="e">The event args.</param>
        public virtual void OnNodeCollapsed(NodeCollapsedEventArgs<BigXmlNode> e) {
            var nodeCollapsed = this.NodeCollapsed;
            if(nodeCollapsed != null) {
                nodeCollapsed(this, e);
            }
        }

        /// <summary>
        /// Executed when raw xml changes.
        /// </summary>
        /// <param name="e">The event args.</param>
        public virtual void OnRawXmlChanged(RawXmlChangedEventArgs e) {
            var rawXmlChanged = this.RawXmlChanged;
            if(rawXmlChanged != null) {
                rawXmlChanged(this, e);
            }
        }

        /// <summary>
        /// Executed when path for the document is needed (before save).
        /// </summary>
        /// <param name="e">The event args.</param>
        public virtual void OnDocumentNameRequired(DocumentNameRequiredEventArgs e) {
            var documentNameRequired = this.DocumentNameRequired;
            if(documentNameRequired != null) {
                documentNameRequired(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.NodeUpdated"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnNodeUpdated(NodeUpdatedEventArgs<BigXmlNode> e) {
            var nodeUpdated = this.NodeUpdated;
            if(nodeUpdated != null) {
                nodeUpdated(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.NodeAdded"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnNodeAdded(NodeAddedEventArgs<BigXmlNode> e) {
            var nodeAdded = this.NodeAdded;
            if(nodeAdded != null) {
                nodeAdded(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.RootElementAdded"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnRootElementAdded(RootElementAddedEventArgs<BigXmlNode> e) {
            var rootElementAdded = this.RootElementAdded;
            if(rootElementAdded != null) {
                rootElementAdded(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.NodeRemoved"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnNodeRemoved(NodeRemovedEventArgs<BigXmlNode> e) {
            var nodeRemoved = this.NodeRemoved;
            if(nodeRemoved != null) {
                nodeRemoved(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.AttributeAdded"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnAttributeAdded(AttributeAddedEventArgs<BigXmlNode, IBigXmlAttribute> e) {
            var attributeAdded = this.AttributeAdded;
            if(attributeAdded != null) {
                attributeAdded(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.AttributeRemoved"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnAttributeRemoved(AttributeRemovedEventArgs<BigXmlNode, IBigXmlAttribute> e) {
            var attributeRemoved = this.AttributeRemoved;
            if(attributeRemoved != null) {
                attributeRemoved(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.Closing"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnClosing(EventArgs e) {
            var closing = this.Closing;
            if(closing != null) {
                closing(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.Closed"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnClosed(EventArgs e) {
            var closed = this.Closed;
            if(closed != null) {
                closed(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.Saving"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnSaving(EventArgs e) {
            var saving = this.Saving;
            if(saving != null) {
                saving(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.Saved"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnSaved(EventArgs e) {
            var saved = this.Saved;
            if(saved != null) {
                saved(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.Validating"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnValidating(ValidationEventArgs e) {
            var validating = this.Validating;
            if(validating != null) {
                validating(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.StatusUpdated"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnStatusUpdated(StatusUpdatedEventArgs e) {
            var statusUpdated = this.StatusUpdated;
            if(statusUpdated != null) {
                statusUpdated(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.NodesSelected"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnNodesSelected(NodesSelectedEventArgs<BigXmlNode> e) {
            var nodesSelected = this.NodesSelected;
            if(nodesSelected != null) {
                nodesSelected(this, e);
            }
        }

        /// <summary>
        /// Invokes the <see cref="IXmlModel.SubtreeUpdated"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnSubtreeUpdated(SubtreeUpdatedEventArgs<BigXmlNode> e) {
            var subtreeUpdated = this.SubtreeUpdated;
            if(subtreeUpdated != null) {
                subtreeUpdated(this, e);
            }
        }

        /// <summary>
        /// Invokes the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">Name of the changed property.</param>
        public virtual void OnPropertyChanged(string propertyName) {
            var propertyChanged = this.PropertyChanged;
            if(propertyChanged != null) {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Raises the <see cref="E:ProgressChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/> instance containing the event data.</param>
        public virtual void OnProgressChanged(ProgressChangedEventArgs e) {
            var progressChanged = this.ProgressChanged;
            if(progressChanged != null) {
                progressChanged(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:SaveCompleted"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        public virtual void OnSaveCompleted(EventArgs e) {
            var saveCompleted = this.SaveCompleted;
            if(saveCompleted != null) {
                saveCompleted(this, e);
            }
        }

        /// <summary>
        /// Invoked when root element has been replaced by some other element.
        /// </summary>
        /// <param name="e">The event data.</param>
        public virtual void OnRootElementChanged(RootElementChangedEventArgs e) {
            var rootElementChanged = this.RootElementChanged;
            if(rootElementChanged != null) {
                rootElementChanged(this, e);
            }
        }
        #endregion

        #region Helper
        /// <summary>
        /// Counts the number of nodes of the subtree at the specified address.
        /// </summary>
        /// <param name="address">Address of the root of the subtree.</param>
        /// <returns>Number of nodes.</returns>
        public int CountNodes(int[] address) {
            return this.xmlDocument.Reader.CountNodes(address);
        }

        public void SetModified(bool modified) {
            this.Modified = modified;
        }

        /// <summary>
        /// Synchronizes collection of loaded nodes with the actual tree. This is helper method.
        /// </summary>
        /// <remarks>Sometimes happens that nodes actually loaded and those in the collection get out of sync so this method
        ///  helps to keep it in order.</remarks>
        public void SyncNodes() {
            this.Nodes.Clear();
            this.AddNodeWithChildren(root as BigXmlNodeInternal);
        }
        #endregion
        #endregion

        #region Protected
        /// <summary>
        /// Closes the xmlModel.
        /// </summary>
        protected void OnClose() {
            this.xmlDocument = null;
            this.path = string.Empty;
        }

        /// <summary>
        /// Invoked when validating.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        protected void OnValidating(object sender, ValidationEventArgs e) {
            this.OnValidating(e);
        }
        #endregion

        #region Private
        /// <summary>
        /// Invoked when xsd validation is completed.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        private void OnValidateXsdAsyncCompleted(object sender, RunWorkerCompletedEventArgs e) {
            var validateXsdCompleted = this.ValidateXsdCompleted;
            if(validateXsdCompleted != null) {
                validateXsdCompleted(this, e);
            }
        }

        /// <summary>
        /// Invoked when xsd validation should be performed.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        private void DoValidateXsdAsync(object sender, DoWorkEventArgs e) {
            var settings = e.Argument as XmlReaderSettings;
            if(settings != null) {
                this.Validate(settings, sender as BackgroundWorker, e);

                settings.ValidationEventHandler -= OnValidating;
            }
        }

        /// <summary>
        /// Invoked when DTD validation is completed.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        private void OnValidateDtdAsyncCompleted(object sender, RunWorkerCompletedEventArgs e) {
            var validateDtdCompleted = this.ValidateDtdCompleted;
            if(validateDtdCompleted != null) {
                validateDtdCompleted(this, e);
            }
        }

        /// <summary>
        /// Invoked when DTD validation should be performed.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        private void DoValidateDtdAsync(object sender, DoWorkEventArgs e) {
            var settings = e.Argument as XmlReaderSettings;
            if(settings != null) {
                this.Validate(settings, sender as BackgroundWorker, e);
                settings.ValidationEventHandler -= OnValidating;
            }
        }

        /// <summary>
        /// Validates the document by reading it using special <see cref="XmlReaderSettings"/>.
        /// </summary>
        /// <param name="settings"><see cref="XmlReaderSettings"/> to use for reading the document.</param>
        /// <param name="worker">Background worker used when calling this method.</param>
        /// <param name="e">The event data of the worker.</param>
        private void Validate(XmlReaderSettings settings, BackgroundWorker worker, DoWorkEventArgs e) {
            string tempPath = this.path + Guid.NewGuid();
            int i = 0;
            try {
                this.SaveCore(tempPath, false);
                using(XmlReader r = XmlReader.Create(tempPath, settings)) {
                    while(r.Read()) {
                        if(worker != null && worker.CancellationPending) {
                            e.Cancel = true;
                            return; // cancel the operation
                        } else {
                            i++;
                            if(i == 10) {
                                Thread.Sleep(settings.ValidationType == ValidationType.DTD ? 0 : 5);
                                i = 0;
                            }
                        }
                    }
                }
            } finally {
                File.Delete(tempPath);
            }
        }

        /// <summary>
        /// Expands the specified node. Internal implementation.
        /// </summary>
        /// <param name="node">Node to expand.</param>
        /// <param name="range">Range which to expand.</param>
        private void ExpandNodePrivate(BigXmlNodeInternal node, string range = "") {
            int[] address = node.Address;
            if(node.OriginalAddress != null && node.Address.SequenceEqual(node.OriginalAddress)) {
                address = node.Address;
            } else if(node.OriginalAddress != null && !node.Address.SequenceEqual(node.OriginalAddress)) {
                address = node.OriginalAddress;
            }

            int nodeToExpandChildCount = node.TotalChildCount; //this.xmlDocument.Reader.GetChildCount(address);
            IEnumerable<int> toExpand;
            if(string.IsNullOrEmpty(range)) {
                // get all child nodes
                // todo Maybe if there are too many child nodes, show some kind of warning...like Are you sure???
                toExpand = from n in Enumerable.Range(1, nodeToExpandChildCount) select n;
            } else {
                toExpand = range.ParseRange();
            }

            // not null OriginalAddress means that this node is from actual file
            if(node.OriginalAddress != null) {
                if(node.NodeState == ExpandState.Collapsed) {
                    var modified = node.Modified; // save the parent's modified state
                    var childNodes = this.xmlDocument.GetChildNodes(node, toExpand); // get the child nodes from the underlying document
                    node.ChildNodes.Clear();

                    foreach(var child in childNodes) {
                        node.ChildNodes.Add(child);

                        if(child != null) {
                            // add to dictionary
                            this.AddNode(child.XmlNode, child);

                            node.XmlNode.AppendChild(child.XmlNode);
                            child.SetParent(node);
                            child.SetModified(false);
                        }
                    }

                    // this.parent.Expand(this.range, childCount);
                    // node.TotalChildCount = nodeToExpandChildCount;
                    node.SetModified(modified); // restore the parent's modified state
                } // when node has already been expanded
                else if(node.NodeState == ExpandState.PartiallyExpanded) {
                    // select only child nodes that are not already expanded
                    var alreadyExpanded = node.ExpandedRange;
                    var userWantstoExpand = toExpand;

                    toExpand = userWantstoExpand.Except(alreadyExpanded); // from x in userWantstoExpand where !alreadyExpanded.Contains(x) select x;

                    var childNodes = this.xmlDocument.GetChildNodes(node, toExpand);

                    for(int i = 0; i < childNodes.Count(); ++i) {
                        var child = childNodes.ElementAt(i);
                        if(child != null) {
                            // add to dictionary
                            this.AddNode(child.XmlNode, child);

                            node.ChildNodes[i] = child;
                            child.SetParent(node);
                            child.SetModified(false);
                        }
                    }
                } else {
                    throw new InvalidOperationException("This node has already been expanded.");
                }
            } else {
                throw new InvalidOperationException("Cannot expand newly created nodes.");
            }
        }

        /// <summary>
        /// Internal implementation of collapse. Basically removes the child nodes of the collapsing node.
        /// </summary>
        /// <param name="node">Node to collapse.</param>
        private void CollapseNodePrivate(BigXmlNode node) {
            if(!node.CanCollapse()) {
                throw new InvalidOperationException("There is modified child node, cannot collapse. Save the document before.");
            }

            var nodeInternal = CheckNode(node);
            // every child node loses its parent & parent loses its children
            foreach(var child in nodeInternal.ChildNodes) {
                if(child != null) {
                    var childInternal = CheckNode(child);
                    childInternal.SetParent(null);

                    // remove from dictionary
                    this.RemoveNodes(childInternal);

                    nodeInternal.XmlNode.RemoveChild(childInternal.XmlNode);
                }
            }

            // clear BigXmlNode's child nodes
            int count = node.ChildNodes.Count;
            node.ChildNodes.Clear();
            for(int i = 0; i < count; ++i) {
                node.ChildNodes.Add(null);
            }
        }

        /// <summary>
        /// Removes node and all its descendant nodes from the collection of loaded nodes.
        /// </summary>
        /// <param name="parent"></param>
        private void RemoveNodes(BigXmlNodeInternal parent) {
            foreach(BigXmlNodeInternal child in parent.ChildNodes.Where(x => x != null)) {
                this.RemoveNodes(child);
            }

            this.RemoveNode(parent.XmlNode);
        }

        //private static void RemoveNode(BigXmlNode node) {
        //    foreach(var child in node.ChildNodes) {
        //        if(child != null) {
        //            RemoveNode(child);
        //        }
        //    }
        //}

        /// <summary>
        /// Tries to cast <see cref="BigXmlNode"/> to <see cref="BigXmlNodeInternal"/>.
        /// If it can't be cast it means the node didn't come from this xmlModel and the exception is thrown. 
        /// </summary>
        /// <param name="node"><see cref="BigXmlNode"/> to check and cast.</param>
        /// <returns>Cast node if everything went ok.</returns>
        private static BigXmlNodeInternal CheckNode(BigXmlNode node) {
            if(node == null) {
                return null;
            }

            if(!(node is BigXmlNodeInternal)) {
                throw new ArgumentException("Current node instance wasn't created in this xmlModel.");
            }

            return node as BigXmlNodeInternal;
        }

        #endregion
        #endregion

        #region Events
        /// <summary>
        /// Occurs when node gets expanded.
        /// </summary>
        public event EventHandler<NodeExpandedEventArgs<BigXmlNode>> NodeExpanded;

        /// <summary>
        /// Occurs when node gets collapsed.
        /// </summary>
        public event EventHandler<NodeCollapsedEventArgs<BigXmlNode>> NodeCollapsed;

        /// <summary>
        /// Occurs when raw xml gets changed (document gets changed).
        /// </summary>
        public event EventHandler<RawXmlChangedEventArgs> RawXmlChanged;

        /// <summary>
        /// Occurs when user wants to save the document but there is no path set.
        /// </summary>
        public event EventHandler<DocumentNameRequiredEventArgs> DocumentNameRequired;

        /// <summary>
        /// Occurs when some node is updated.
        /// </summary>
        public event EventHandler<NodeUpdatedEventArgs<BigXmlNode>> NodeUpdated;

        /// <summary>
        /// Occurs when subtree of the document gets changed and view needs to update its state.
        /// </summary>
        public event EventHandler<SubtreeUpdatedEventArgs<BigXmlNode>> SubtreeUpdated;

        /// <summary>
        /// Occurs when node has been added.
        /// </summary>
        public event EventHandler<NodeAddedEventArgs<BigXmlNode>> NodeAdded;

        /// <summary>
        /// Occurs when root element has been added.
        /// </summary>
        public event EventHandler<RootElementAddedEventArgs<BigXmlNode>> RootElementAdded;

        /// <summary>
        /// Occurs when node has been removed.
        /// </summary>
        public event EventHandler<NodeRemovedEventArgs<BigXmlNode>> NodeRemoved;

        /// <summary>
        /// Occurs when attribute has been added.
        /// </summary>
        public event EventHandler<AttributeAddedEventArgs<BigXmlNode, IBigXmlAttribute>> AttributeAdded;

        /// <summary>
        /// Occurs when attribute has been removed.
        /// </summary>
        public event EventHandler<AttributeRemovedEventArgs<BigXmlNode, IBigXmlAttribute>> AttributeRemoved;

        /// <summary>
        /// Occurs when xmlModel is closing.
        /// </summary>
        public event EventHandler<EventArgs> Closing;

        /// <summary>
        /// Occurs when xmlModel is closed.
        /// </summary>
        public event EventHandler<EventArgs> Closed;

        /// <summary>
        /// Occurs before document is saved.
        /// </summary>
        public event EventHandler<EventArgs> Saving;

        /// <summary>
        /// Occurs when document is saved.
        /// </summary>
        public event EventHandler<EventArgs> Saved;

        /// <summary>
        /// Occurs when xmlModel is validating the document.
        /// </summary>
        public event EventHandler<ValidationEventArgs> Validating;

        /// <summary>
        /// Occurs when status message is updated.
        /// </summary>
        public event EventHandler<StatusUpdatedEventArgs> StatusUpdated;

        /// <summary>
        /// Occurs when Xsd validation has completed.
        /// </summary>
        public event EventHandler<RunWorkerCompletedEventArgs> ValidateXsdCompleted;

        /// <summary>
        /// Occurs when DTD validation has completed.
        /// </summary>
        public event EventHandler<RunWorkerCompletedEventArgs> ValidateDtdCompleted;

        /// <summary>
        /// Occurs when node(s) have been selected.
        /// </summary>
        public event EventHandler<NodesSelectedEventArgs<BigXmlNode>> NodesSelected;

        public event EventHandler<RootElementChangedEventArgs> RootElementChanged;

        /// <summary>
        /// Occurs when some property of this xmlModel changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when [progress changed].
        /// </summary>
        public event EventHandler<ProgressChangedEventArgs> ProgressChanged;

        /// <summary>
        /// Occurs when [save completed].
        /// </summary>
        public event EventHandler SaveCompleted;
        #endregion
    }
}